#type vertex
#version 330 core
layout (location = 0) in vec3 a_Position;
layout (location = 1) in vec2 a_TexCoord;
layout (location = 2) in vec3 a_Normal;
layout (location = 3) in vec3 a_Tangent;

uniform mat4 project_view;
uniform mat4 model;

out vec2 TexCoord;
out vec3 FragPos;
out vec3 FragNormal;
out mat3 TBN;

void main() {
    TexCoord = a_TexCoord;
    FragNormal = mat3(transpose(inverse(model))) * a_Normal;
    FragPos = vec3(model * vec4(a_Position, 1.0));
    vec3 T = normalize(vec3(model * vec4(a_Tangent, 0.0)));
    vec3 N = normalize(vec3(model * vec4(a_Normal, 0.0)));
    T = normalize(T - dot(T, N) * N);
    vec3 B = cross(T, N);
    TBN = mat3(T, B, N);

    gl_Position = project_view * model * vec4(a_Position, 1.0);
}

#type fragment
#version 330 core
layout (location = 0) out vec4 FragColor;

struct Material {
    sampler2D diffuse_texture;
    sampler2D specular_texture;
    sampler2D emissive_texture;
    sampler2D normal_texture;

    vec3 diffuse;
    vec3 specular;
    vec3 emission;

    float shininess;
};

struct PhongSection {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

struct DirectLight {
    vec3 direction;
    PhongSection phong;
};

struct DotLight {
    vec3 position;
    PhongSection phong;

    float constant;
    float linear;
    float quadratic;
};

struct SpotLight {
    vec3 position;
    vec3 direction;
    PhongSection phong;

    float inner_cutoff;     // radians
    float outer_cutoff;     // radians
};

in vec2 TexCoord;
in vec3 FragNormal;
in vec3 FragPos;
in mat3 TBN;

vec3 Normal;

uniform vec4 color;
uniform vec3 view_pos;

uniform Material material;
uniform DirectLight dirlight;
uniform DotLight dotlight;
uniform SpotLight spotlight;

vec3 CalculatePhongSectionAmbient(PhongSection phong) {
    return phong.ambient;
}

vec3 CalculatePhongSectionDiffuse(PhongSection phong, vec3 lightDir) {
    vec3 viewDir = normalize(view_pos - FragPos);
    if (dot(viewDir, Normal) < 0)
        return vec3(0, 0, 0);
    float diff = max(dot(Normal, lightDir), 0.0);
    vec3 diffuse = phong.diffuse * diff * material.diffuse;
    return diffuse;
}

vec3 CalculatePhongSectionSpecular(PhongSection phong, vec3 lightDir) {
    vec3 viewDir    = normalize(view_pos - FragPos);
    vec3 halfwayDir = normalize(lightDir + viewDir);
    float spec = 0;
    if (material.shininess != 0 && dot(viewDir, Normal) > 0) {
        spec = pow(max(dot(Normal, halfwayDir), 0.0), material.shininess);
    }
    vec3 specular = phong.specular * spec * material.specular;
    return specular;
}

vec3 CalculateDirectLight(DirectLight light) {
    vec3 lightDir = normalize(-light.direction);

    vec3 ambient = CalculatePhongSectionAmbient(light.phong);
    vec3 diffuse = CalculatePhongSectionDiffuse(light.phong, lightDir);
    vec3 specular = CalculatePhongSectionSpecular(light.phong, lightDir);

    return (ambient + diffuse) * texture(material.diffuse_texture, TexCoord).rgb +
        specular * texture(material.specular_texture, TexCoord).rgb;
}

vec3 CalculateDotLight(DotLight light) {
    float distance = length(light.position - FragPos);
    float division = light.constant + light.linear * distance + light.quadratic * (distance * distance);
    float attenuation = 0;
    vec3 ambient, diffuse, specular;

    vec3 lightDir = normalize(light.position - FragPos);
    if (division != 0) {
        attenuation = 1.0 / division;
        ambient = CalculatePhongSectionAmbient(light.phong);
        diffuse = CalculatePhongSectionDiffuse(light.phong, lightDir);
        specular = CalculatePhongSectionSpecular(light.phong, lightDir);
    }

    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;

    return (ambient + diffuse) * texture(material.diffuse_texture, TexCoord).rgb +
        specular * texture(material.specular_texture, TexCoord).rgb;
}

vec3 CalculateSpotLight(SpotLight light) {
    vec3 color;
    vec3 lightDir = normalize(-light.direction);
    vec3 ambient, diffuse, specular;

    ambient = CalculatePhongSectionAmbient(light.phong);

    float theta = dot(normalize(light.position - FragPos), normalize(lightDir));

    if(theta > light.outer_cutoff) {

        float epsilon   = light.inner_cutoff - light.outer_cutoff;
        float intensity = clamp((theta - light.outer_cutoff) / epsilon, 0.0, 1.0);

        diffuse = intensity * CalculatePhongSectionDiffuse(light.phong, lightDir);
        specular = intensity * CalculatePhongSectionSpecular(light.phong, lightDir);

        color = (ambient + diffuse) * texture(material.diffuse_texture, TexCoord).rgb +
            specular * texture(material.specular_texture, TexCoord).rgb;
    } else {
        color = ambient * texture(material.diffuse_texture, TexCoord).rgb;
    }

    return color;
}

void main() {
    Normal = texture(material.normal_texture, TexCoord).rgb;
    Normal = normalize(Normal * 2.0 - 1.0);
    Normal = normalize(TBN * Normal);

    if (texture(material.diffuse_texture, TexCoord).a == 0)
        discard;

    FragColor = vec4(
            CalculateDirectLight(dirlight) +
            CalculateDotLight(dotlight) +
            CalculateSpotLight(spotlight) +
            texture(material.emissive_texture, TexCoord).rgb * material.emission,
            1.0) * color;
}
